namespace SimpleWebServices.Data
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Diagnostics.CodeAnalysis;
    using System.IO;
    using System.Net;
    using System.Web;
    using System.Xml;
    using System.Xml.XPath;
    using SimpleWebServices.Configuration;

    public sealed class XmlDataObjectCollection : Collection<XmlDataObject>
    {
        public static XmlDataObjectCollection Load(string path)
        {
            XmlDataObjectCollection result = null;

            DirectoryInfo directory = new DirectoryInfo(path);
            if (directory.Exists)
            {
                result = Load(directory);
            }
            else
            {
                result = Load(new FileInfo(path));
            }

            return result;
        }

        [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Directories and file are handled differently.")]
        public static XmlDataObjectCollection Load(DirectoryInfo directory)
        {
            if (null == directory)
            {
                throw new ArgumentNullException("directory");
            }
            else if (!directory.Exists)
            {
                throw new FileNotFoundException("The directory was not found.", directory.FullName);
            }

            XmlDataObjectCollection result = new XmlDataObjectCollection();

            foreach (string file in Directory.GetFiles(directory.FullName, "*.object.xml", SearchOption.AllDirectories))
            {
                result.Add(Load(new FileInfo(file)));
            }

            return result;
        }

        [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Directories and file are handled differently.")]
        public static XmlDataObjectCollection Load(FileInfo file)
        {
            if (null == file)
            {
                throw new ArgumentNullException("file");
            }
            else if (!file.Exists)
            {
                throw new FileNotFoundException("The file was not found.", file.FullName);
            }

            XmlDocument xml = new XmlDocument();
            using (FileStream stream = File.Open(file.FullName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
            {
                using (StreamReader reader = new StreamReader(stream))
                {
                    xml.LoadXml(reader.ReadToEnd());
                }
            }

            return Load(xml);
        }

        public static XmlDataObjectCollection Load(IXPathNavigable xml)
        {
            if (null == xml)
            {
                throw new ArgumentNullException("xml");
            }

            XmlDataObjectCollection result = new XmlDataObjectCollection();

            XmlDataObject defaults = GetDefaults(xml);
            if (null != defaults)
            {
                XPathNodeIterator iterator = xml.CreateNavigator().Select("/objects/object");
                while (iterator.MoveNext())
                {
                    result.Add(ToXmlDataObject(iterator, defaults));
                }
            }

            return result;
        }

        public void Add(IEnumerable<XmlDataObject> items)
        {
            if (null == items)
            {
                throw new ArgumentNullException("items");
            }

            foreach (XmlDataObject item in items)
            {
                this.Add(item);
            }
        }

        public void Add(DirectoryInfo directory)
        {
            this.Add(XmlDataObjectCollection.Load(directory));
        }

        public void Add(FileInfo file)
        {
            this.Add(XmlDataObjectCollection.Load(file));
        }

        public void Upsert()
        {
            foreach (XmlDataObject obj in this)
            {
                IRepository repository = RepositoryConfiguration.Settings()[obj.ObjectType.Name];
                IRecord record = obj.ToRecord();
                if (!record.Token.HasValue)
                {
                    record.Token = repository.Token(record.Urn);
                }

                repository.Upsert(record);
            }
        }

        private static XmlDataObject GetDefaults(IXPathNavigable xml)
        {
            XmlDataObject result = null;

            XPathNodeIterator iterator = xml.CreateNavigator().Select("/objects[1]");
            while (iterator.MoveNext())
            {
                result = ToXmlDataObject(iterator, new XmlDataObject());
            }

            return result;
        }

        private static XmlDataObject ToXmlDataObject(XPathNodeIterator iterator, XmlDataObject defaults)
        {
            XmlDataObject result = null;

            if (null != iterator)
            {
                result = new XmlDataObject
                {
                    Cacheability = defaults.Cacheability,
                    Created = defaults.Created,
                    Expires = defaults.Expires,
                    Modified = defaults.Modified,
                    Status = defaults.Status,
                    Token = defaults.Token,
                    ObjectType = defaults.ObjectType,
                    Urn = defaults.Urn,
                };

                if ("object".Equals(iterator.Current.Name, StringComparison.OrdinalIgnoreCase))
                {
                    XmlDocument xml = new XmlDocument();
                    xml.LoadXml(iterator.Current.InnerXml);

                    result.Xml = xml;
                }

                if (iterator.Current.MoveToFirstAttribute())
                {
                    do
                    {
                        switch (iterator.Current.Name)
                        {
                            case "cacheability":
                                result.Cacheability = (HttpCacheability)Enum.Parse(typeof(HttpCacheability), iterator.Current.Value);
                                break;

                            case "created":
                                result.Created = XmlConvert.ToDateTime(iterator.Current.Value, XmlDateTimeSerializationMode.Utc);
                                break;

                            case "expires":
                                result.Expires = iterator.Current.Value;
                                break;

                            case "modified":
                                result.Modified = XmlConvert.ToDateTime(iterator.Current.Value, XmlDateTimeSerializationMode.Utc);
                                break;

                            case "status":
                                result.Status = (HttpStatusCode)Enum.Parse(typeof(HttpStatusCode), iterator.Current.Value);
                                break;

                            case "token":
                                result.Token = iterator.Current.Value;
                                break;

                            case "type":
                                result.ObjectType = Type.GetType(iterator.Current.Value, true);
                                break;

                            case "urn":
                                result.Urn = iterator.Current.Value;
                                break;

                            default:
                                break;
                        }
                    }
                    while (iterator.Current.MoveToNextAttribute());
                }
            }

            return result;
        }
    }
}